// WWSimpleMovieView is exactly that: a pretty simple view for showing movies.
// It has a bunch of different behaviors to do all the obvious things:
// all of the following do it at some given frames/second:
// - plays movie either from the beginning each time or bounces
//   - plays movie continuously
//   - starts playing movie once when clicked, stops when clicked
//   - plays movie when the mouse is over it; resets when the mouse leaves

// one thing that would be handy is if the view had a notion of a
//control panel for itself.  It might have two outlets, one which is the
//controlPanelView, and some other view (say, a WW3DWell) could come
//along and switch that view in so that the view might have some visible
//control.  This is sort of a bug of NS; no notion of objects having
//control panels (i.e. the live version of an inspector).

#import "WWSimpleMovieView.h"
#import "aspectRatios.h"
// for IB protocols
#import <apps/InterfaceBuilder.h>

// for mkdir()
#import <sys/types.h>
#import <sys/stat.h>


@implementation WWSimpleMovieView


#define WW_BOTTOMALIGNED  0
#define WW_TOPALIGNED     1


/*
==========================================================================
subext() -- Remove extension from fname only if it is there.

==========================================================================
*/
static char 
*subext(fname, ext)
register char *fname, *ext;
{
  int fl, el;

	for ( fl = strlen(fname), el = strlen(ext);
		--el > -1 && --fl > -1 && fname[fl] == ext[el]; )
		;
	if (el == -1) 
	{  fname[fl] = '\0';
	}
	return(fname);
}


/*
==========================================================================
basename() -- deletes any prefix ending in `/' and the suffix.

==========================================================================
*/
static char 
*basename(str, sfx, dest)
char *str, *sfx, *dest;
{
char temp[1024];
char *p, *p1;
char *subext();

	p = p1 = temp;
	strcpy(p, str);
	while (*p) 
	{  if (*p++ == '/') 
           {  p1 = p;
	   }
	}
	strcpy (dest, subext(p1, sfx));
	return(dest);
}

+ initialize { return [WWSimpleMovieView setVersion:3], self; }


- showFrameAndIncrement:sender
{
   if (drawCorrectly && (alpha < 1.0))
   {  clearTheView = YES; [self display];
      clearTheView = NO;  [window display];
   }
   else
   {  clearTheView = NO;  [self display];
   }
   NXPing();
   if (!stationaryFrameTE)  // if we're not really playing, we're outta here
   {  return self;
   }

   // check to see if we're at the last frame of the sequence
   if (goingForward)
   {  if (currentFrameIndex == (frameCount - 1)) // we're at the end!
      {  if (loop)
         {  currentFrameIndex = 0;
         }
         else
         {  currentFrameIndex--;
            goingForward = NO;
         }
      }
      else // more frames to go...
      {  currentFrameIndex++;
      }
   }
   else // going backwards; 0 is the end frame
   {  if (!currentFrameIndex) // we're at the end!
      {  if (loop)  // no way; we don't backwards if we're looping, doofus...
         {  NXLogError("shouldn't be here, pal.\n");
         }
         goingForward = YES;
         currentFrameIndex++;
      }
      else // keep counting down
      {  currentFrameIndex--;
      }
   }

   movieImage = [imageList objectAt:currentFrameIndex];
   return self;
}

void nextFrame(DPSTimedEntry teNumber, double now, void *userData)
{
  id  myObj = (id)userData;

  [myObj showFrameAndIncrement:nil];
  return ;
}



- beginStationaryBehavior
{
  // make sure that I have some images in my list
  if (![imageList count])
  {  return self;
  }
  goingForward = YES;
  stationaryFrameTE = DPSAddTimedEntry(frameTimeIncr, 
				       (DPSTimedEntryProc)nextFrame, 
				       self, NX_MODALRESPTHRESHOLD);
  if (stationaryBehavior == WW_PLAY_MOUSE_IN)
  {  [self setMyTrackingRect:YES];
  }
  return self;
}

- endStationaryBehavior
{
  if (stationaryFrameTE)
  {  DPSRemoveTimedEntry(stationaryFrameTE);
     stationaryFrameTE = 0;
  }

  return self;
}

- copyFromZone:(NXZone *)zone
{
  id newCopy = [super copyFromZone:zone];

  if (frameCount)
  {  // rather than copy through the movie data, we'll just 
     // mark both this instance and the new one as using
     // a shared movie
     [self setMovieIsShared:YES];
     [newCopy setMovieIsShared:YES];
  }

  return newCopy;
}

- free
{
  if (controlPanel)
  {  [controlPanel setFreeWhenClosed:NO];
     [controlPanel close];
     [controlPanel free];
     controlPanel = nil;
  }
  [self endStationaryBehavior];
  if (stationaryBehavior == WW_PLAY_MOUSE_IN)
  {  [self setMyTrackingRect:NO];
  }

  if (!movieIsShared)
  {  [imageList freeObjects];
  }
  [imageList free];

  if (!imageIsShared)
  {  [image free];
  }

  return [super free];
}

- initFrame:(const NXRect *)r 
{
   [super initFrame:r];
   backgroundColor = NXConvertRGBToColor(0.96, 1.0, 0.6);
   borderType = 0;
   alpha = 1.0;
   image = nil;
   movieImage = nil;
   imageList = [[List alloc] init];
   imageIsShared = NO;
   movieIsShared = NO;
   frameCount = 0;
   aspectRatioType = WW_ASPECT_DONT_CARE;
   aspectRatio = 1.0;
   scaleToFit = NO;
   imageUnder = NO;
   horizontalLayoutType = NX_LEFTALIGNED;
   verticalLayoutType = WW_BOTTOMALIGNED;
   fps = 15.0;
   frameTimeIncr = 1.0/fps;
   loop = NO; // in other words, "breathe"
   stationaryBehavior = WW_PLAY_MOUSE_CLICK;
   stationaryFrameTE = (DPSTimedEntry)0;
   sink = NO;
   source = NO;
   archiveImageData = YES;
   archiveMovieData = YES;
   trackingRect = 0;
   drawCorrectly = YES;
   controlPanel = nil;

   if (stationaryBehavior == WW_PLAY_CONTINUOUS)
   {  [self beginStationaryBehavior];
   }

   return self;
}

- awake
{
   [super awake];
   stationaryFrameTE = (DPSTimedEntry)0;
   trackingRect = 0;
   if ([NXApp conformsTo:@protocol(IB)])  // what a hacque!! (def: "hacqe: a particularly baroque (in both senses of the word...) hack")
   {  [self perform:@selector(awakeFromNib) with:nil afterDelay:0.01 cancelPrevious:NO];
      // the reason we do this is because I'm not certain that if we awake in IB our "window" will be set yet.
   }
   currentFrameIndex = 0;
   frameCount = [imageList count];
   controlPanel = nil;
   if (frameCount)
   {  movieImage = [imageList objectAt:currentFrameIndex];
      if (stationaryBehavior == WW_PLAY_CONTINUOUS)
      {  [self beginStationaryBehavior];
      }
   }

   return self;
}

- awakeFromNib
{
  if (stationaryBehavior == WW_PLAY_MOUSE_IN)
  {  [self setMyTrackingRect:YES];
  }
  return self;
}


#define EPSILON 10

- sizeTo:(NXCoord)width :(NXCoord)height
{
  // don't want to make it too small...
  if (width < EPSILON) return nil;
  if (height < EPSILON) return nil;


  // let's say it comes in at 100, 100 and you're supposed to be constrained to 4:3
  // you don't want it to get bigger than 100, 100, so... you frob the width, not the height
  switch (aspectRatioType)
  {  case WW_ASPECT_DONT_CARE:
            aspectRatio = width/height;
            break;
     case WW_ASPECT_NTSC:  // 1.33:1
            height = width/1.33;
            aspectRatio = 1.33;
            break;
     case WW_ASPECT_SQUARE:  // 1:1
            if (width < height)
	    {  height = width;
	    }
	    else
	    {  width = height;
	    }
            aspectRatio = 1.0;
            break;
     case WW_ASPECT_CUSTOM:  //aspectX:aspectY
            if (aspectRatio < 1.0)
	    {  width = height * aspectRatio;
	    }
	    else
	    {  height = width/aspectRatio;
	    }
            break;
     default:
            NXLogError("unknown aspect ratio type: %d", aspectRatioType);
            break;
  }

  [super sizeTo:width :height];
  [self display];
  if (stationaryBehavior == WW_PLAY_MOUSE_IN)
  {  [self setMyTrackingRect:YES];
  }

  return self;
}

- setBorderType:(int)newBorderType { borderType = newBorderType; return self; }
- (int)borderType { return borderType; }

- setFPS:(float)newFPS { fps = newFPS; frameTimeIncr = 1.0/fps; return self; }
- (float)fps { return fps; }

//
- sizeToImage:sender 
{
   NXSize s;


   if (image)
   {  [image getSize:&s];
      [self sizeTo:s.width :s.height];
   }
   return self;
}

- sizeToMovie:sender 
{
   NXSize s;


   if (movieImage)
   {  [movieImage getSize:&s];
      [self sizeTo:s.width :s.height];
   }
   return self;
}

- setScaleToFit:(BOOL)flag { scaleToFit = flag; return self; }
- (BOOL)scaleToFit { return scaleToFit; }
- setImageUnder:(BOOL)flag { imageUnder = flag; return self; }
- (BOOL)imageUnder { return imageUnder; }

- (int)aspectRatioType { return aspectRatioType; }
- (float)aspectRatio { return aspectRatio; }
- setAspectRatioFromMatrix:sender
{
  NXRect  newRect;


  [self getFrame:&newRect];
  aspectRatioType = [[sender selectedCell] tag];
  switch (aspectRatioType)
  {  case WW_ASPECT_DONT_CARE:
            break;
     case WW_ASPECT_NTSC:  // 1.33:1
            newRect.size.height = newRect.size.width/1.33;
            break;
     case WW_ASPECT_SQUARE:  // 1:1
            if (newRect.size.width < newRect.size.height)
            {  newRect.size.height = newRect.size.width;
	    }
	    else
	    {  newRect.size.width = newRect.size.height;
	    }
            break;
     case WW_ASPECT_CUSTOM:  //aspectX:aspectY
            if (aspectRatio < 1.0)
	    {  newRect.size.width = newRect.size.height * aspectRatio;
	    }
	    else
	    {  newRect.size.height = newRect.size.width/aspectRatio;
	    }
            break;
     default:
            NXLogError("unknown aspect ratio type: %d", aspectRatioType);
            break;
  }
  [self sizeTo:newRect.size.width :newRect.size.height];

  return self;
}
//
- setAspectRatio:sender
{
  NXRect  newRect;


  [self getFrame:&newRect];
  aspectRatio = [sender floatValue];

  if (aspectRatioType != WW_ASPECT_CUSTOM)
  {  return self;
  }
  if (aspectRatio < 1.0)
  {  newRect.size.width = newRect.size.height * aspectRatio;
  }
  else
  {  newRect.size.height = newRect.size.width/aspectRatio;
  }
  [self sizeTo:newRect.size.width :newRect.size.height];

  return self;
}
//
- setHorizontalLayoutFromMatrix:sender  {  horizontalLayoutType = [[sender selectedCell] tag];  return self; }
- (int)horizontalLayoutType { return horizontalLayoutType; }

- setVerticalLayoutFromMatrix:sender  {  verticalLayoutType = [[sender selectedCell] tag];  return self; }
- (int)verticalLayoutType { return verticalLayoutType; }


- (NXColor) backgroundColor 	{ return backgroundColor; }
- setBackgroundColor:(NXColor)c { return backgroundColor = c, self; }
- (float) alpha			{ return alpha; }
- setBackgroundAlpha:(float)n	{ return alpha = n, self; }

- (int)stationaryBehavior	{ return stationaryBehavior; }
- setStationaryBehavior:(int)n	
{  
  [self endStationaryBehavior];
  stationaryBehavior = n;
  if (stationaryBehavior == WW_PLAY_CONTINUOUS)
  {  [self beginStationaryBehavior];
  }
  if (stationaryBehavior == WW_PLAY_MOUSE_IN)
  {  [self setMyTrackingRect:YES];
  }
  return self; 
}
- setStationaryBehaviorFromMatrix:sender 
{ 
  [self setStationaryBehavior:[[sender selectedCell] tag]]; 
  return self; 
}

- setLoop:(BOOL)n { loop = n; return self; }
- (BOOL)loop { return loop; }

- setArchiveImageData:(BOOL)n { archiveImageData = n; return self; }
- (BOOL)archiveImageData { return archiveImageData; }

- setArchiveMovieData:(BOOL)n { archiveMovieData = n; return self; }
- (BOOL)archiveMovieData { return archiveMovieData; }

- setDrawCorrectly:(BOOL)n { drawCorrectly = n; return self; }
- (BOOL)drawCorrectly { return drawCorrectly; }

- (int)frameCount { return frameCount; }

- setImageIsShared:(BOOL)flag { imageIsShared = flag; return self; } 
- setMovieIsShared:(BOOL)flag { movieIsShared = flag; return self; } 

- removeMovie:sender
{
  if (!movieIsShared)
  {  [imageList freeObjects];
     movieImage = nil;
  }
  else
  {  [imageList empty];
     movieImage = nil;
  }
  return self;
  
}

- setAnimDir:(const char *)filename 
{
   BOOL  done = NO;
   char  theBaseName[MAXPATHLEN+1], 
         tiffFileName[MAXPATHLEN+1], 
         *animSuffixPtr;
   int   frameIndex = 1;// remind me to slap Keith next time I see him...


   if (filename && *filename)
   {  if (!movieIsShared)
      {  [imageList freeObjects];
      }
      movieImage = nil;
      done = NO;
      animSuffixPtr=rindex(filename, '.');
      if (animSuffixPtr && strcmp(animSuffixPtr, ".anim"))
      {  NXLogError("the file %s doesn't end in anim, dude!\n", filename);
         return nil;
      }
      basename(filename, ".anim", theBaseName);  
      while (!done)
      {  sprintf(tiffFileName, "%s/%s.%d.tiff", filename, theBaseName, frameIndex);
         // does the file exist?
         if (access(tiffFileName, R_OK) == -1) 
         {  done = YES;  // nope; we're outta here
         }
         else
         {  movieImage = [NXImage findImageNamed:tiffFileName];
            if (!movieImage) 
            {  movieImage = [[NXImage alloc] init];
               if (archiveMovieData)
               {  [movieImage setDataRetained:YES];
               }
               if (![movieImage loadFromFile:tiffFileName])  
               {  NXLogError("unable to load image from file <%s>\n", tiffFileName);
                  done = YES; // guess the last image was bad; a common occurrence when the anim is still being generated...
       	       }
            }
            [imageList insertObject:movieImage at:(frameIndex - 1)]; // I really gotta whap Keith; 1 based indices; jeez...
            frameIndex++;
         }
      }
   }
   frameCount = [imageList count];
   currentFrameIndex = 0;
   movieImage = [imageList objectAt:currentFrameIndex];
   clearTheView = YES; [self display];
   clearTheView = NO;  [window display];
   NXPing();
   if (stationaryBehavior == WW_PLAY_CONTINUOUS)
   {  [self beginStationaryBehavior];
   }

   return self;
}



- removeImage:sender
{
   if (!imageIsShared)
   {  [image free];
   }
   image = nil;
   return self;
}

- setImageFile:(const char *)filename 
{
    if (!imageIsShared)
    {  [image free];
    }
    image = nil;
    if (filename && *filename)
    { image = [NXImage findImageNamed:filename];
      if (!image) 
      {  image = [[NXImage alloc] init];
         [image setDataRetained:YES];
         if (![image loadFromFile:filename])
         {  NXLogError("unable to load image from file <%s>\n", filename);
            return nil; 
 	 }
      }
    }
    clearTheView = YES; [self display];
    clearTheView = NO;  [window display];
    NXPing();
    return self;
}

- setImage:i  
{  
   if (!imageIsShared)
   {  [image free];
   }
   image = i; 
   clearTheView = YES; [self display];
   clearTheView = NO;  [window display];
   NXPing();
   return self; 
}
- image { return image; }

- movieImage { return movieImage; }


- drawSelf:(const NXRect *)theRect :(int)count
{
  NXPoint  p = {0.0, 0.0};
  NXSize   imageSize, movieSize;


  if (clearTheView)  
  {  NXSetColor(backgroundColor);
     PSsetalpha(0.0);
     PScompositerect(theRect->origin.x, theRect->origin.y, 
  		     theRect->size.width, theRect->size.height,
  		     NX_SOVER);
     return self;
  }

  NXSetColor(backgroundColor);
  if (!frameCount || drawCorrectly) // if there's no movie or we're being anal, use my alpha value
  {  PSsetalpha(alpha);
  }
  else // otherwise, make it opaque, since the other views in the window aren't being redrawn too
  {  PSsetalpha(1.0);
  }

  PScompositerect(theRect->origin.x, theRect->origin.y, 
	          theRect->size.width, theRect->size.height,
  		  NX_SOVER);

  [image getSize:&imageSize];
  [movieImage getSize:&movieSize];

  switch (verticalLayoutType)
  {  case WW_BOTTOMALIGNED:
          p.y = 0.0;
	  break;
     case WW_TOPALIGNED:
          if (imageSize.height > movieSize.height)
          {  p.y = theRect->size.height - imageSize.height;
 	  }
          else
          {  p.y = theRect->size.height - movieSize.height;
 	  }
 	  break;
    default:
          p.y = 0.0;
	  break;
  }
  switch (horizontalLayoutType)
  {  case NX_LEFTALIGNED:
          p.x = 0.0;
	  break;
     case NX_RIGHTALIGNED:
          if (imageSize.width > movieSize.width)
          {  p.x = theRect->size.width - imageSize.width;
	  }
	  else
          {  p.x = theRect->size.width - movieSize.width;
	  }
 	  break;
     case NX_CENTERED:
          if (imageSize.width > movieSize.width)
          {  if (theRect->size.width > imageSize.width)
             {  p.x = (theRect->size.width - imageSize.width)/2.0;
             }
             else
             {  p.x = 0.0;
             }
	  }
          else
          {  if (theRect->size.width > movieSize.width)
             {  p.x = (theRect->size.width - movieSize.width)/2.0;
             }
             else
             {  p.x = 0.0;
             }
	  }

          if (imageSize.height > movieSize.height)
          {  if (theRect->size.height > imageSize.height)
             {  p.y = (theRect->size.height - imageSize.height)/2.0;
             }
             else
             {  p.y = 0.0;
             }
 	  }
          else
          {  if (theRect->size.height > movieSize.height)
             {  p.y = (theRect->size.height - movieSize.height)/2.0;
             }
             else
             {  p.y = 0.0;
             }
 	  }
	  break;
    default:
          p.x = 0.0;
	  break;
  }

  if (imageUnder)
  {  [image composite:NX_SOVER toPoint:&p];
     [movieImage composite:NX_SOVER toPoint:&p];
  }
  else
  {  [movieImage composite:NX_SOVER toPoint:&p];
     [image composite:NX_SOVER toPoint:&p];
  }

  if (borderType)
  {  NXRect  r = *theRect;


     PSsetalpha(1.0);

     r.origin.x++; r.origin.y--;        
     NXSetColor(NX_COLORBLACK); NXFrameRect(&r);

     r.origin.x--, r.origin.y++;        
     NXSetColor(NX_COLORGRAY); NXFrameRect(&r);

     r.origin.x--; r.size.width++; r.size.height++; 
     NXSetColor(NX_COLORWHITE); NXFrameRect(&r);
  }

  return self;
}

- (BOOL)acceptsFirstResponder { return YES; }

- setMyTrackingRect:(BOOL)flag
{
  NXRect  visible;


  if (trackingRect)
  {  [window discardTrackingRect:trackingRect];
     trackingRect = 0;
  }
  if (flag)
  {  if ([self getVisibleRect:&visible])
     {  [self convertRect:&visible toView:nil];
        [window setTrackingRect:&visible inside:NO owner:self tag:(int)self left:NO right:NO];
        trackingRect = (int)self;
     }
  }
  return self;
}

// might want to allow alt-click to move only one frame...
- mouseDown:(NXEvent *)theEvent
{
  if (theEvent->flags & NX_COMMANDMASK)  // show the control panel
  {  [self loadControlPanel];
     return self;
  }
  if (stationaryBehavior == WW_PLAY_MOUSE_CLICK)
  {  if (!stationaryFrameTE)  // is it running?
     {  if (theEvent->flags & NX_ALTERNATEMASK)  // click one frame
        {  stationaryFrameTE = (DPSTimedEntry)1; // set it to something so the frame gets incremented...
           [self showFrameAndIncrement:nil];
           stationaryFrameTE = 0;
        }
        else
        {  [self beginStationaryBehavior];  // start it up
        }
     }
     else
     {  [self endStationaryBehavior];  // stop it
     }
  }
  return self;
}

- mouseEntered:(NXEvent *)theEvent
{
  if (!stationaryFrameTE && (stationaryBehavior == WW_PLAY_MOUSE_IN))
  {  [window makeFirstResponder:self]; // do I need to do this?
     [self beginStationaryBehavior];
  }
  return self;
}

- mouseExited:(NXEvent *)theEvent
{
  if (stationaryFrameTE && (stationaryBehavior == WW_PLAY_MOUSE_IN))
  {  [self endStationaryBehavior];
  }
  return self;
}

- (const char *)getInspectorClassName { return "WWSimpleMovieViewIBInspector"; }


// Control Panel stuff (stolen from my IBInspector


- loadControlPanel
{
   char buf[MAXPATHLEN + 1];
   id bundle;

   
   // we want to support two different things here.  
   // 1st scenario: lots of MovieViews, screen space is expensive, we want to have these control panels 
    
   if (!controlPanel)
   {  bundle = [NXBundle bundleForClass:[self class]];
      [bundle getPath:buf forResource:"WWSimpleMovieViewControlPanel" ofType:"nib"];
      [NXApp loadNibFile:buf owner:self withNames:NO fromZone:[self zone]];
      if (!controlPanel)
      {  NXLogError("problem loading WWSimpleMovieViewControlPanel\n");
         return nil;
      }
      [controlPanel setDelegate:self];
   }
   [self revertControlPanel:nil];
   [controlPanel makeKeyAndOrderFront:self];
   return self;
}


- revertControlPanel:sender 
{ 
   [theColor setColor:[self backgroundColor]];
   [alphaSlider setFloatValue:[self alpha]];
   if ([self drawCorrectly])
   {  [alphaSlider setEnabled:YES];
   }
   else
   {  if ( [self frameCount])
      {  [alphaSlider setEnabled:NO];
      }
      else
      {  [alphaSlider setEnabled:YES];
      }
   }
   if ([self archiveImageData])
   {  [theImage setStringValue:""];
   }
   else
   {  [theImage setStringValue:[[self image] name]];
   }

   if ([self archiveMovieData])
   {  [theImage setStringValue:""];
   }
   else
   {  [theMovie setStringValue:[[self movieImage] name]];
   }

   [drawCorrectlySwitch setIntValue:[self drawCorrectly]];
   [aspectRatioMatrix selectCellWithTag:[self aspectRatioType]];
   [horizontalLayoutMatrix selectCellWithTag:[self horizontalLayoutType]];
   [verticalLayoutMatrix selectCellWithTag:[self verticalLayoutType]];
   [customAspectRatioText setFloatValue:[self aspectRatio]];
   [customAspectRatioText setEnabled:([self aspectRatioType] == WW_ASPECT_CUSTOM)];
   [scaleSwitch setIntValue:(int)[(WWSimpleMovieView *)self scaleToFit]];
   [imageUnderSwitch setIntValue:[self imageUnder]];
   [rotateTo setFloatValue:[self frameAngle]];
   [borderTypeMatrix selectCellWithTag:[self borderType]];
   [stationaryBehaviorMatrix selectCellWithTag:[self stationaryBehavior]];
   [loopSwitch setIntValue:[self loop]];
   [archiveMovieDataSwitch setIntValue:[self archiveMovieData]];
   [archiveImageDataSwitch setIntValue:[self archiveImageData]];

   [fpsText setFloatValue:[self fps]];
   clearTheView = YES; [self display];
   clearTheView = NO;  [window display]; NXPing();

   return self;
}

- takeColor_:s	{ [self setBackgroundColor:[theColor color]]; [self revertControlPanel:nil]; return self; }
- setAlpha_:s	{ [self setBackgroundAlpha:[s floatValue]]; [self revertControlPanel:nil]; return self; }
- setImage_:s	{ [self setImageFile:(char *)[s stringValue]]; [self revertControlPanel:nil]; return self; }
- setMovie_:s	{ [self setAnimDir:(char *)[s stringValue]]; [self revertControlPanel:nil]; return self; }
- setAspectRatio_:s	{ [self setAspectRatio:s]; [self revertControlPanel:nil]; return self; }
- setImageUnder_:s	{ [self setImageUnder:[s intValue]]; [self revertControlPanel:nil]; return self; }
- setScale_:s	{ [self setScaleToFit:[s intValue]]; [self revertControlPanel:nil]; return self; }
- setAspectRatioFromMatrix_:s	{ [self setAspectRatioFromMatrix:s]; [self revertControlPanel:nil]; return self; }
- setHorizontalLayoutFromMatrix_:s	{ [self setHorizontalLayoutFromMatrix:s]; [self revertControlPanel:nil]; return self; }
- setBorderTypeFromMatrix_:s	{ [self setBorderType:[[s selectedCell] tag]]; [self revertControlPanel:nil]; return self; }
- setVerticalLayoutFromMatrix_:s	{ [self setVerticalLayoutFromMatrix:s]; [self revertControlPanel:nil]; return self; }
- setFPS_:s	{ [self setFPS:[s floatValue]]; [self revertControlPanel:nil]; return self; }
- setStationaryBehaviorFromMatrix_:s	{ [self setStationaryBehaviorFromMatrix:s]; [self revertControlPanel:nil]; return self; }
- setLoop_:s	{ [self setLoop:[s intValue]]; [self revertControlPanel:nil]; return self; }
- setArchiveMovieData_:s	{ [self setArchiveMovieData:[s intValue]]; [self revertControlPanel:nil]; return self; }
- setArchiveImageData_:s	{ [self setArchiveImageData:[s intValue]]; [self revertControlPanel:nil]; return self;  }
- setDrawCorrectly_:s	{ [self setDrawCorrectly:[s intValue]]; [self revertControlPanel:nil]; return self; }

- rotateTo_:s { [self rotateTo:[s floatValue]];  [self display]; [self revertControlPanel:nil]; return self; }

- sizeToImage_:s { [self sizeToImage:s];  [self display]; [self revertControlPanel:nil]; return self; }
- sizeToMovie_:s { [self sizeToMovie:s];  [self display]; [self revertControlPanel:nil]; return self; }

- saveImage:sender
{
  static id savePanel=nil;
  NXStream *ts;


  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }

  [savePanel setRequiredFileType:"tiff"];
  if([savePanel runModal])
  {  ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);
     [image writeTIFF:ts allRepresentations:NO usingCompression:NX_TIFF_COMPRESSION_LZW andFactor:1.0];
     NXSaveToFile(ts, [savePanel filename]);
     NXCloseMemory(ts,NX_FREEBUFFER);
  }

  return self;
}

- saveMovie:sender
{
  static 
      id    savePanel=nil;
  NXStream  *ts;
  int       ret, i;
  char      imageNameBase[MAXPATHLEN+1];
  char      imageFilename[MAXPATHLEN+1];
  char      *animFilename;


  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }

  [savePanel setRequiredFileType:"anim"];
  if([savePanel runModal])
  {  // this is a little dangerous, but hey, it's faster...
     animFilename = (char *)[savePanel filename];
     ret = mkdir(animFilename, 0777);
     if (ret)
     {  NXLogError("couldn't save anim to %s", animFilename);
        return nil;
     }
     basename(animFilename, ".anim", imageNameBase);

     for (i = 1; i < frameCount; i++)
     {  ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);
        if (!ts)
        {  NXLogError("unable to open a memory stream!\n");
           return nil;
        }
        if (![[imageList objectAt:(i-1)] writeTIFF:ts allRepresentations:NO usingCompression:NX_TIFF_COMPRESSION_LZW andFactor:1.0])
        {  NXLogError("unable to write image %d to a memory stream!\n", i);
           NXCloseMemory(ts, NX_FREEBUFFER);
           return nil;
        }
        sprintf(imageFilename, "%s/%s.%d.tiff", animFilename, imageNameBase, i);
        if (NXSaveToFile(ts, imageFilename))
        {  NXLogError("unable to save memory stream to file <%s>\n", imageFilename);
           NXCloseMemory(ts, NX_FREEBUFFER);
           return nil;
        }
        NXCloseMemory(ts, NX_SAVEBUFFER);
     }
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);
     if (!ts)
     {  NXLogError("unable to open a memory stream!\n");
        return nil;
     }
     if (![[imageList objectAt:(i-1)] writeTIFF:ts allRepresentations:NO usingCompression:NX_TIFF_COMPRESSION_LZW andFactor:1.0])
     {  NXLogError("unable to write image %d to a memory stream!\n", i);
        NXCloseMemory(ts, NX_FREEBUFFER);
        return nil;
     }
     sprintf(imageFilename, "%s/%s.%d.tiff", animFilename, imageNameBase, i);
     if (NXSaveToFile(ts, imageFilename))
     {  NXLogError("unable to save memory stream to file <%s>\n", imageFilename);
        NXCloseMemory(ts, NX_FREEBUFFER);
        return nil;
     }
     NXCloseMemory(ts, NX_FREEBUFFER);
  }

  return self;
}


- windowWillClose:sender
{
  if (controlPanel && (sender == controlPanel))
  { controlPanel = nil;
  }
  return self;
}



#define typeVectorVersion1 "@fifcciiiffcicccc@"
#define typeValuesVersion1 &image, &alpha, &aspectRatioType, &aspectRatio, &imageUnder, &scaleToFit, &borderType, &horizontalLayoutType, &verticalLayoutType, &fps, &frameTimeIncr, &loop, &stationaryBehavior, &sink, &source, &archiveImageData, &archiveMovieData, &imageList

#define typeVectorVersion2 "@fifcciiiffcicccc@c"
#define typeValuesVersion2 &image, &alpha, &aspectRatioType, &aspectRatio, &imageUnder, &scaleToFit, &borderType, &horizontalLayoutType, &verticalLayoutType, &fps, &frameTimeIncr, &loop, &stationaryBehavior, &sink, &source, &archiveImageData, &archiveMovieData, &imageList, &drawCorrectly

#define typeVector "@fifcciiiffcicccc@ccc"
#define typeValues &image, &alpha, &aspectRatioType, &aspectRatio, &imageUnder, &scaleToFit, &borderType, &horizontalLayoutType, &verticalLayoutType, &fps, &frameTimeIncr, &loop, &stationaryBehavior, &sink, &source, &archiveImageData, &archiveMovieData, &imageList, &drawCorrectly, &imageIsShared, &movieIsShared

- read:(NXTypedStream*)stream 
{
    int version;
    [super read:stream];


    version = NXTypedStreamClassVersion(stream, "WWSimpleMovieView");
    if (version == 0) NXReadTypes(stream, "i", &version), version=1;
    if (version == 1)
    {  NXReadTypes(stream, typeVectorVersion1, typeValuesVersion1);
       backgroundColor = NXReadColor(stream);
       drawCorrectly = YES;
       imageIsShared = NO;
       movieIsShared = NO;
    } 
    if (version == 2)
    {  NXReadTypes(stream, typeVectorVersion2, typeValuesVersion2);
       backgroundColor = NXReadColor(stream);
       imageIsShared = NO;
       movieIsShared = NO;
    } 
    if (version == 3)
    {  NXReadTypes(stream, typeVector, typeValues);
       backgroundColor = NXReadColor(stream);
    } 
    return self;
}


- write:(NXTypedStream *)stream 
{
    [super write:stream];
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteColor(stream, backgroundColor);

    return self;
}

    

@end
